package com.jimo.app;

import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.widget.Toast;

import androidx.activity.EdgeToEdge;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.apk.axml.aXMLDecoder;
import com.apk.axml.aXMLEncoder;
import com.jimo.api.ApiConstant;
import com.jimo.app.databinding.ActivityInjectBinding;
import com.jimo.app.util.ApkUtils;
import com.jimo.app.util.Util;


import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class InjectActivity extends AppCompatActivity {

    private @NonNull ActivityInjectBinding bind;
    private String package_name;

    @RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        bind= ActivityInjectBinding.inflate(getLayoutInflater());
        setContentView(bind.getRoot());
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        bind.install.setOnClickListener(v -> Util.installApk(this));
        bind.uninstall.setOnClickListener(v ->{
            String originPath=getExternalCacheDir().getAbsolutePath()+"/origin_sign.apk";
            File f = new File(originPath);
            if(!f.exists()){
                Toast.makeText(this,"请先选择app进行注入",Toast.LENGTH_SHORT).show();
                return;
            }
            if(Util.isAppInstalled(this,package_name)){
                Util.uninstallApp(this,package_name);
            }
        });

        ActivityResultLauncher<Intent> launcher =
                registerForActivityResult(
                        new ActivityResultContracts.StartActivityForResult(),
                        result -> {
                            if (result.getResultCode() == RESULT_OK) {
                                Intent data = result.getData();
                                if (data != null) {
                                    String apkPath = data.getStringExtra("sourceDir");
                                    new Thread(() -> startToParse(apkPath)).start();
                                }
                            }
                        });

        bind.select.setOnClickListener(v -> {
            Intent intent = new Intent(InjectActivity.this, AppListActivity.class);
            intent.putExtra("selectInjected",false);
            launcher.launch(intent);
        });
    }


    @RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
    private void startToParse(String apkPath) {
        String originPath=getExternalCacheDir().getAbsolutePath()+"/origin.apk";
        runOnUiThread(() -> bind.injectStatus.setText("[复制]将apk复制到"+originPath+"\n\n"));
        try {
            Files.copy(Paths.get(apkPath), Paths.get(originPath), StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        runOnUiThread(() -> bind.injectStatus.append("[解压]解压apk到内存中...\n\n"));
        try {
            ZipFile zipFile = new ZipFile(apkPath);
            Enumeration<? extends ZipEntry> entries = zipFile.entries();
            Set<String> abis = new HashSet<>();
            Set<String> dexs = new HashSet<>();
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                String name = entry.getName();
                if (name.startsWith("lib/") && name.endsWith(".so")) {
                    String[] parts = name.split("/");
                    if (parts.length >= 2) {
                        abis.add(parts[1]);
                    }
                }
                if(name.startsWith("classes")){
                    dexs.add(name);
                }
            }
            Map<String, byte[]> files = new HashMap<>();
            String dynamicPermission = "com.bytecat.algui.PERMISSION";
            String mManifest = new aXMLDecoder(zipFile.getInputStream(zipFile.getEntry("AndroidManifest.xml"))).decode().trim();
            runOnUiThread(() -> bind.injectStatus.append("[manifest]解析成功\n\n"));
            Document doc = DocumentHelper.parseText(mManifest);
            Element root = doc.getRootElement();
            String versionName = root.attributeValue("versionName");
            package_name=root.attributeValue("package");
            if(versionName.contains("inject_")){
                runOnUiThread(() -> bind.injectStatus.append("[错误]已经注入过了\n\n"));
                return;
            }
            root.addAttribute("versionName","inject_"+versionName);
            Util. addDynamicPermission(root,dynamicPermission);
            runOnUiThread(() -> bind.injectStatus.append("[manifest]添加动态ipc权限:"+dynamicPermission+"\n\n"));
            Element appElement = root.element("application");
            appElement.addAttribute("android:debuggable","false");
            appElement.addAttribute("android:isTestOnly","false");
            appElement.addAttribute("android:extractNativeLibs","true");
            if (appElement == null) {
                runOnUiThread(() -> bind.injectStatus.append("[manifest]没找到application标签，停止注入\n\n"));
                return;
            }

            Util.addService(appElement, ApiConstant.CommandServiceName, dynamicPermission, "true", ":other", "true", ApiConstant.CommandServiceName, true);
            runOnUiThread(() -> bind.injectStatus.append("[manifest]添加服务:"+ApiConstant.CommandServiceName+"\n\n"));
            Util.addService(appElement, ApiConstant.HostServiceName, null, null, null, "true", ApiConstant.HostServiceName, false);
            runOnUiThread(() -> bind.injectStatus.append("[manifest]添加服务:"+ApiConstant.HostServiceName+"\n\n"));
            files.put("AndroidManifest.xml",new aXMLEncoder().encodeString(this, doc.asXML().trim()));
            files.putAll(Util.getSo(this,abis));
            runOnUiThread(() -> bind.injectStatus.append("[SO库]添加"+String.join("、",abis)+"\n\n"));
//            runOnUiThread(() -> bind.injectStatus.append("[反编译DEX]开始\n\n"));
//            String dexName=Util.getDex(apkPath,appElement,getExternalCacheDir().getAbsolutePath(),1);
//            runOnUiThread(() -> bind.injectStatus.append("[反编译DEX]已注入初始化函数\n\n"));
//            if(dexName!=null){
//                File dexFile = new File(getExternalCacheDir(), dexName);
//                if(dexFile.exists()){
//                    try(FileInputStream fis=new FileInputStream(dexFile)){
//                        files.put(dexName,fis.readAllBytes());
//                    }catch (Exception ignore){}
//                    dexFile.delete();
//                }
//            }
            files.put("classes"+(dexs.size()+1)+".dex",Util.getAssetsDex(this));
            runOnUiThread(() -> bind.injectStatus.append("[添加DEX]\n\n"));
            zipFile.close();
            runOnUiThread(() -> bind.injectStatus.append("[打包]开始\n\n"));
            ApkUtils.addOrReplaceFiles(new File(originPath),files);
            File signFile=new File(getExternalCacheDir().getAbsolutePath(),"origin_sign.apk");
            Util.signApks(new File(originPath),signFile,this);
            runOnUiThread(() -> bind.injectStatus.append("[打包]签名完成:"+signFile.getAbsolutePath()+"\n\n"));
            runOnUiThread(() -> bind.injectStatus.append("[打包]修改完毕,请卸载原包后安装\n\n"));
        } catch (Exception e) {
            runOnUiThread(() -> bind.injectStatus.append("报错："+e.getMessage()+"\n\n"));
            e.printStackTrace();
        }
    }
}


